package com.project.common.core.utils;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.util.Assert;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;

/**
 * Utils - RSA加密解密
 *
 * @version 3.0
 */
public final class RSAUtil {

    /**
     * 安全服务提供者
     */
    private static final Provider PROVIDER = new BouncyCastleProvider();

    /**
     * 密钥大小
     */
    private static final int KEY_SIZE = 1024;

    /**
     * 加密解密模式 - 填充
     */
    public static final String RSA_ECB_PKCS1PADDING = "RSA/ECB/PKCS1Padding";

    /**
     * 加密解密模式
     */
    public static final String RSA = "RSA";

    /**
     * 不可实例化
     */
    private RSAUtil() {
    }

    /**
     * 生成密钥对
     *
     * @return 密钥对
     */
    public static KeyPair generateKeyPair() {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", PROVIDER);
            keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());
            return keyPairGenerator.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 通过公钥和数组加密
     *
     * @param publicKey 公钥
     * @param data      数据
     * @return 加密后的数据
     */
    public static byte[] encrypt(PublicKey publicKey, byte[] data) {
        Assert.notNull(publicKey);
        Assert.notNull(data);
        try {
            Cipher cipher = Cipher.getInstance("RSA", PROVIDER);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            int blockSize = cipher.getBlockSize();// 获得加密块大小，如：加密前数据为128个byte，而key_size=1024
            // 加密块大小为127byte,加密后为128个byte;因此共有2个加密块，第一个127byte第二个为1个byte
            int outputSize = cipher.getOutputSize(data.length);// 获得加密块加密后块大小
            int leavedSize = data.length % blockSize;
            int blocksSize = leavedSize != 0 ? data.length / blockSize + 1
                    : data.length / blockSize;
            byte[] raw = new byte[outputSize * blocksSize];
            int i = 0;
            while (data.length - i * blockSize > 0) {
                if (data.length - i * blockSize > blockSize) {
                    cipher.doFinal(data, i * blockSize, blockSize, raw, i
                            * outputSize);
                } else {
                    cipher.doFinal(data, i * blockSize, data.length - i
                            * blockSize, raw, i * outputSize);
                }
                i++;
            }
            return raw;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通过公钥和字符串加密
     *
     * @param publicKey 公钥
     * @param text      字符串
     * @return Base64编码字符串
     */
    public static String encrypt(PublicKey publicKey, String text) {
        Assert.notNull(publicKey);
        Assert.notNull(text);
        byte[] data = encrypt(publicKey, text.getBytes());
        return data != null ? Base64.encodeBase64String(data) : null;
    }

    /**
     * 通过公钥的模量和指数构建公钥后加密字符串
     *
     * @param modulus
     * @param exponent
     * @param text
     * @return
     */
    public static String encrypt(String modulus, String exponent, String text) {
        try {
            byte[] aryExponent = Base64.decodeBase64(exponent);
            byte[] aryModulus = Base64.decodeBase64(modulus);
            BigInteger bigExponent = new BigInteger(1, aryExponent);
            BigInteger bigModulus = new BigInteger(1, aryModulus);
            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(bigModulus, bigExponent);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(keySpec);
            return encrypt(publicKey, text);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通过私钥和数组解密
     *
     * @param privateKey 私钥
     * @param data       数据
     * @return 解密后的数据
     */
    public static byte[] decrypt(PrivateKey privateKey, byte[] data) {
        Assert.notNull(privateKey);
        Assert.notNull(data);
        try {
            Cipher cipher = Cipher.getInstance("RSA", PROVIDER);
            cipher.init(cipher.DECRYPT_MODE, privateKey);
            int blockSize = cipher.getBlockSize();
            ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
            int j = 0;

            while (data.length - j * blockSize > 0) {
                bout.write(cipher.doFinal(data, j * blockSize, blockSize));
                j++;
            }
            return bout.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通过私钥和数组解密 - 有模式
     *
     * @param mode       模式
     * @param privateKey 私钥
     * @param data       数据
     * @return 解密后的数据
     */
    public static byte[] decrypt(String mode, PrivateKey privateKey, byte[] data) {
        Assert.notNull(privateKey);
        Assert.notNull(data);
        try {
            Cipher cipher = Cipher.getInstance(mode, PROVIDER);
            cipher.init(cipher.DECRYPT_MODE, privateKey);
            int blockSize = cipher.getBlockSize();
            ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
            int j = 0;

            while (data.length - j * blockSize > 0) {
                bout.write(cipher.doFinal(data, j * blockSize, blockSize));
                j++;
            }
            return bout.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通过私钥和数组解密 - 点位图用
     *
     * @param privateKey 私钥
     * @param data       数据
     * @return 解密后的数据
     */
    public static byte[] decryptForBitmap(PrivateKey privateKey, byte[] data) {
        Assert.notNull(privateKey);
        Assert.notNull(data);
        try {
            //点位图ios端使用了默认的填充模式: RSA/ECB/PKCS1Padding
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", PROVIDER);
            cipher.init(cipher.DECRYPT_MODE, privateKey);
            int blockSize = cipher.getBlockSize();
            ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
            int j = 0;

            while (data.length - j * blockSize > 0) {
                bout.write(cipher.doFinal(data, j * blockSize, blockSize));
                j++;
            }
            return bout.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通过私钥和字符串解密
     *
     * @param privateKey 私钥
     * @param text       Base64编码字符串
     * @return 解密后的数据
     */
    public static String decrypt(PrivateKey privateKey, String text) {
        Assert.notNull(privateKey);
        Assert.notNull(text);
        byte[] data = decrypt(privateKey, Base64.decodeBase64(text));
        return data != null ? new String(data) : null;
    }

    /**
     * 通过私钥和字符串解密 - 有模式
     *
     * @param mode       模式
     * @param privateKey 私钥
     * @param text       Base64编码字符串
     * @return 解密后的数据
     */
    public static String decrypt(String mode, PrivateKey privateKey, String text) {
        Assert.notNull(privateKey);
        Assert.notNull(text);
        byte[] data = decrypt(mode, privateKey, Base64.decodeBase64(text));
        return data != null ? new String(data) : null;
    }

    /**
     * 通过私钥和字符串解密 - 点位图
     *
     * @param privateKey 私钥
     * @param text       Base64编码字符串
     * @return 解密后的数据
     */
    public static String decryptForBitmap(PrivateKey privateKey, String text) {
        Assert.notNull(privateKey);
        Assert.notNull(text);
        byte[] data = decryptForBitmap(privateKey, Base64.decodeBase64(text));
        return data != null ? new String(data) : null;
    }

    /**
     * 通过私钥的模量和指数构建私钥后和字符串解密
     *
     * @param modulus
     * @param exponent
     * @param text
     * @return
     */
    public static String decrypt(String modulus, String exponent, String text) {
        try {
            byte[] aryExponent = Base64.decodeBase64(exponent);
            byte[] aryModulus = Base64.decodeBase64(modulus);
            BigInteger bigExponent = new BigInteger(1, aryExponent);
            BigInteger bigModulus = new BigInteger(1, aryModulus);
            RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(bigModulus, bigExponent);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
            return decrypt(privateKey, text);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        String exponent = "AQAB";
        String modulus = "AKJ7e1Lhn5kBnQQ++UWl8MJn9dGwCoTdXExlOsI6YVZkq4+R+Qb3gNo5v1TgHgfS2EMQ0YChr2//nJdmKc1w8bAz6XPRD4L2ZXnKDTfTOmKcel1jC7CzNUY5M1ahhEQeI6f367loH2me9UwScBN8rtIeGEGhP8E8DVriTk9g1xFv";
        String password = "admin123";
        System.out.print(encrypt(modulus, exponent, password));
    }
}